package main

import "curses"
import "os"
import "bufio"
// import "exec"
import "fmt"
// import "bytes"
// import "io"
import "strings"

// import "markers"
// import "strconv"
// import "parser"
// import "scoping"

var text =
`(1, 2 => x+
`

const KEY_RETURN = 10

func main() {
    startGoCurses();
    defer stopGoCurses();
//
    curses.Init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK);
    curses.Init_pair(2, curses.COLOR_GREEN + 8, curses.COLOR_WHITE + 8)
    curses.Init_pair(3, curses.COLOR_BLUE, curses.COLOR_WHITE + 8)
    curses.Init_pair(4, curses.COLOR_WHITE, curses.COLOR_YELLOW + 8)
    curses.Init_pair(7, curses.COLOR_WHITE + 8, curses.COLOR_RED + 8)
//    
    loop()
}

func startGoCurses() {
    curses.Initscr()
    if curses.Stdwin == nil {
        stopGoCurses()
        os.Exit(1)
    }

    curses.Raw()
    curses.Noecho()
    curses.Stdwin.Keypad(true)
    curses.EnableMouse()

    if err := curses.Start_color(); err != nil {
        fmt.Printf("%s\n", err.String())
        stopGoCurses()
        os.Exit(1)
    }
    curses.InitColor( 7, 500, 500, 0 )
}

func stopGoCurses() {
    curses.Endwin()
}

type ignore int

func (ignore) Do(command string) {}

func loop() {
    w := curses.Stdwin
    w.Clear()
    cols := *curses.Cols;
    rows := *curses.Rows;
    _, _ = rows, cols
//
    c := &ScrollablePanel{}
    e := NewEditPanel(c)
    f := NewFrame(e, e, c)
    s := NewShelf(f, NewFrame(ignore(0), NewEditPanel(nil), nil))
    wp := NewWindowPanel(w,s)
    wp.Resize(rows, cols, 0, 0)
    wp.Refresh()
//
    var focus Panel = e
    for {
        curses.Timeout( 1000 )
        inp := w.Getch()
        // log("inp: ", inp)
        if inp == curses.KEY_MOUSE {
            col, line, _ := curses.MouseEvent()
            focus = wp.FocusForClick(line, col)
        } else {
            if inp != curses.ERR { focus.Key(inp) }
        }
        if cols != *curses.Cols || rows != *curses.Rows {
            cols, rows = *curses.Cols, *curses.Rows
            wp.Resize(rows, cols, 0, 0)
            wp.Refresh()
        }
        if inp == 'q' - 'a' + 1 { break }
        if inp == 'x' - 'a' + 1 { wp.Refresh() }
    }
}

// --------------------------------------------------------------------

var logLine int

func log(items ...interface{}) {
    var COL = *curses.Cols / 2 + 10
    tag := "| "
    s := fmt.Sprint(items...)
    curses.Stdwin.Addstr(COL, logLine, "  ", int32(0))
    lines := strings.Split(s, "\n", -1)
    for _, line := range lines {
        logLine += 1
        curses.Stdwin.Addstr(COL, logLine, tag + line + "  ", int32(0))
        if logLine > 20 { logLine = 0 }
    }
    curses.Stdwin.Addstr(COL, logLine, "> ", int32(0))
    curses.Stdwin.Refresh()
}

// --------------------------------------------------------------------

type Scrollable interface {
    FillIn(canSee, outOf, startingAt int)
}

type ScrollablePanel struct {
    Common
}

func (s *ScrollablePanel) Refresh() {
    s.FillIn(1, 1, 0)
}

func (s *ScrollablePanel) FillIn(canSee, outOf int, startingAt int) {
    if outOf == 0 { outOf = 1 }
    if canSee > outOf { canSee = outOf }
    if startingAt > outOf { startingAt = outOf }
    lines := s.lines
//
    for i := 0; i < lines; i += 1 { s.Put(i, 0, '*') }
//
    s.Put(0, 0, curses.ACS_VLINE)
    s.Put(1, 0, curses.ACS_BTEE)
//
    free := lines - 6
    linesToShow := canSee * free / outOf + 1
    start := 2 + startingAt * (free - linesToShow) / outOf
    end := start + linesToShow
//
    log("                                                               ")
    log("canSee ", canSee, " out of ", outOf, " lines.")
    log("starting at ", startingAt)
    log("lines = ", lines, ", linesToShow = ", linesToShow)
    log("start = ", start, ", end = ", end)
//
    for i := 2; i < start; i += 1 { s.Put(i, 0, ' ') }
//
    s.Put(start, 0, curses.ACS_TTEE)
    for i := start + 1; i < end; i += 1 { s.Put(i, 0, curses.ACS_VLINE) }
    s.Put(end, 0, curses.ACS_BTEE)
    for i := end + 1; i < lines - 2; i += 1 { s.Put(i, 0, ' ') }
//
    s.Put(lines-2, 0, curses.ACS_TTEE)
    s.Put(lines-1, 0, curses.ACS_VLINE)
}

// --------------------------------------------------------------------

type Panel interface {
    Key(rune int)
    Resize(lines, cols int, lineOffset, colOffset int)
    FocusForClick(line, col int) Panel
    Cursor(line, col int)
    Parent(p Panel)
    Refresh()
    Put(line, col int, rune int)
    PutString(line, col int, text string)
}

type Common struct {
    parent Panel
    lines, cols int
    lineOffset, colOffset int
}

func (c *Common) Parent(p Panel) {
    c.parent = p
}

func (c *Common) Resize(lines, cols int, lineOffset, colOffset int) {
    c.lines, c.cols = lines, cols
    c.lineOffset, c.colOffset = lineOffset, colOffset
}

func (c *Common) Cursor(line, col int) {
    c.parent.Cursor(line + c.lineOffset, col + c.colOffset) // TODO
}

func (c *Common) Put(line, col int, rune int) {
    c.parent.Put(line + c.lineOffset, col + c.colOffset, rune)
}

func (c *Common) PutString(line, col int, text string) {
    c.parent.PutString(line + c.lineOffset, col + c.colOffset, text)
}

//// --------------------------------------------------------------------

type Shelf struct {
    Common
    l, r Panel
}

func NewShelf(x, y Panel) Panel {
    shelf := &Shelf{Common{nil, 0, 0, 0, 0}, x, y}
    x.Parent(shelf)
    y.Parent(shelf)
    return shelf
}

func (s *Shelf) FocusForClick(line, col int) Panel {
    if col < s.cols/2 { return s.l.FocusForClick(line, col) }
    return s.r.FocusForClick(line, col - s.cols/2)
}

func (s *Shelf) Key(rune int) {
}

func (s *Shelf) Refresh() {
    s.l.Refresh()
    s.r.Refresh()
}

func (s *Shelf) Resize(lines, cols int, lineOffset, colOffset int) {
    s.Common.Resize(lines, cols, lineOffset, colOffset)
    s.l.Resize(lines, cols/2, 0, 0)
    s.r.Resize(lines, cols - cols/2, 0, cols/2)
}

// -----------------------------------------------------------------------------------
type Doer interface {
    Do(command string)
}

type HeaderPanel struct {
    EditPanel
    do Doer
}

func NewHeaderPanel(d Doer) Panel {
    return &HeaderPanel{do: d}
}

func (h *HeaderPanel) FocusForClick(line, col int) Panel {
    h.Cursor(line, col)
    return h
}

func (h *HeaderPanel) Resize(lines, cols int, loff, coff int) {
    h.EditPanel.Resize(lines, cols - 3, 0, coff + 3)  
}

func (h *HeaderPanel) Refresh() {
    h.EditPanel.Refresh()
    h.Put(0, -3, curses.ACS_HLINE)
    h.Put(0, -2, curses.ACS_RTEE)
    for col := 2; col < h.cols; col += 1 { h.Put(0, col, curses.ACS_HLINE) }
}

func (h *HeaderPanel) Key(rune int) {
    // log("Keystroke in HeaderPanel", rune)
    if rune == KEY_RETURN {
        // log("RETURN on HeaderPanel: ", h.EditPanel.content[0])
        command := h.EditPanel.content[0]
        h.EditPanel.content[0] = ""
        h.EditPanel.line, h.EditPanel.col = 0, 0
        h.Refresh()
        h.Cursor(0, 0)
        h.do.Do(command)
    } else {
        h.EditPanel.Key(rune)
    }
}

// ----------------------------------------------------------------------

type FramePanel struct {
    Common
    inside Panel
    header Panel
    scroll *ScrollablePanel
}

func NewFrame(d Doer, inside Panel, scroll *ScrollablePanel) Panel {
    h := NewHeaderPanel(d)
    f := &FramePanel{inside: inside, header: h, scroll: scroll}
    if scroll != nil { scroll.Parent(f) }
    inside.Parent(f)
    h.Parent(f)
    return f
}

func (f *FramePanel) FocusForClick(line, col int) Panel {
    if 0 < line && line < f.lines - 1 && 0 < col && col < f.cols - 1 { 
        return f.inside.FocusForClick(line - 1, col - 1) 
    }
    if line == 0 && 0 < col && col < f.cols - 2 {
        return f.header.FocusForClick(0, col - 1)
    }
    return f 
}

func (f *FramePanel) Refresh() {
    f.inside.Refresh()
    f.header.Refresh()
    lines, cols := f.Common.lines, f.Common.cols
    for col := 0; col < cols; col += 1 { f.Put(lines-1, col, curses.ACS_HLINE) }
    for line := 0; line < lines; line += 1 { f.Put(line, 0, curses.ACS_VLINE) }
    if f.scroll == nil {
        for line := 0; line < lines; line += 1 { 
            f.Put(line, cols-1, curses.ACS_VLINE) 
        }
    } else {
        f.scroll.Refresh()
    }
    f.Put(0, 0, curses.ACS_ULCORNER)
    f.Put(0, cols-1, curses.ACS_URCORNER)
    f.Put(lines-1, 0, curses.ACS_LLCORNER)
    f.Put(lines-1, cols-1, curses.ACS_LRCORNER)
}

func (f *FramePanel) Cursor(line, col int) {
    f.Common.parent.Cursor(line, col)
}

func (f *FramePanel) Key(rune int) {
}

func (f *FramePanel) Resize(lines, cols int, lineOffset, colOffset int) {
    f.Common.Resize(lines, cols, lineOffset, colOffset)
    f.inside.Resize(lines - 2, cols - 2, 1, 1)
    f.header.Resize(1, cols - 2, 0, 1)
    if f.scroll != nil { f.scroll.Resize(lines - 2, 1, lineOffset + 1, cols - colOffset - 1) }
}

//// --------------------------------------------------------------------

type EditPanel struct {
    Common
    target Scrollable
    line, col int       // position within document
    scrollOffset int    // index of top line in window
    content []string
}

func NewEditPanel(target Scrollable) *EditPanel {
    return &EditPanel{Common: Common{}, target: target, content: []string{}}
}

func chop(command string) (fun, arg string) {
    i := strings.IndexRune(command, ' ')
    if i < 0 { return command, "" }
    return command[0:i], command[i+1:]
}

func (e *EditPanel) loadFile(fileName string) {
    log("loadFile '", fileName, "'")
    r, err := os.Open(fileName)
    if err != nil {
        log("BOOM -- ", err)
        return
    }
    defer r.Close()
    e.content = []string{}
    br := bufio.NewReader( r )
    for {
        s, err := br.ReadString('\n')
        if err == os.EOF { break }
        e.content = append(e.content, s)
    }
    e.Refresh()
}

func (e *EditPanel) Do(command string) {
    fun, arg := chop(command)
    if fun == "r" { e.loadFile( strings.TrimSpace(arg) ) 
    } else 
        log("EditPanel.Do(", fun, " ", arg, ")")
}

func (e *EditPanel) Cursor(line, col int) {
    e.Common.Cursor(line, col)
    e.line, e.col = line + e.scrollOffset, col
}

func (e *EditPanel) FocusForClick(line, col int) Panel {
    e.Cursor(line, col)
    return e
}

func (e *EditPanel) Refresh() {
    for i := 0; i < min(len(e.content), e.lines); i += 1 {
        j := i + e.scrollOffset
        x := ""
        if j < len(e.content) { x = e.content[j] }
        e.PutString(i, 0, x)
        e.Clear(i, len(x))
    }
    e.Cursor(e.line - e.scrollOffset, e.col)
}

func (e *EditPanel) makeSpace() {
    for len(e.content) < e.line + 1 { e.content = append(e.content, "") }
    for len(e.content[e.line]) < e.col + 1 { e.content[e.line] += " " }
}

func (e *EditPanel) Clear(line, col int) {
    for i := col; i < e.cols; i += 1 { e.Put(line, i, ' ') }
}

func (e *EditPanel) Key(rune int) {
    switch rune {
    case curses.KEY_UP:
        if e.line > e.scrollOffset {
            e.line -= 1
        } else if e.scrollOffset > 0 {
            e.scrollOffset -= 1
            e.line -= 1
            e.Refresh()
        }

    case curses.KEY_DOWN:
        if e.line < e.lines - 1 {
            e.line += 1
        } else {
            e.scrollOffset += 1
            e.line += 1
            e.Refresh()
        }

    case curses.KEY_LEFT:
        if e.col > 0 { e.col -= 1}

    case curses.KEY_RIGHT:
        if e.col < e.cols - 1 { e.col += 1}

    case curses.KEY_DC:
        e.col += 1
        fallthrough

    case curses.KEY_BACKSPACE:
        if e.line < len(e.content) && e.col < len(e.content[e.line]) && e.col > 0 {
            x := e.content[e.line]
            updated := x[:e.col - 1] + x[e.col:]
            e.content[e.line] = updated
            e.col -= 1
            e.PutString(e.line - e.scrollOffset, 0, updated)
        }

    case KEY_RETURN: 
        if e.line > len(e.content) - 1 || e.line >= e.lines -1 { break }
        e.makeSpace()
        e.content = append(e.content, "")
        copy(e.content[e.line+2:], e.content[e.line+1:])
        e.content[e.line + 1] = e.content[e.line][e.col:]
        e.content[e.line] = e.content[e.line][:e.col]
        soff := e.scrollOffset
        for i := e.line; i < min(len(e.content), e.lines); i += 1 {
            e.PutString(i - soff, 0, e.content[i] )
            e.Clear(i - soff, len(e.content[i]))
        }
        e.line += 1
        e.col = 0

    default:
        if rune < ' ' || rune > 126 || e.col >= e.cols - 1 {
            log("unhandled key ", rune)
            break
        }
        e.makeSpace()
        character := string([]byte{byte(rune)})
        x := e.content[e.line]
        updated := x[:e.col] + character + x[e.col:] 
        e.content[e.line] = updated
        e.col += 1
        e.PutString(e.line - e.scrollOffset, 0, updated)

    }
    if e.target != nil { e.target.FillIn(e.lines, len(e.content), e.scrollOffset) }
    e.Cursor(e.line - e.scrollOffset, e.col)
}

func min(x, y int) int {
    if x < y { return x }
    return y
}

//func (e *EditPanel) compile() {
//    log("doing 'compile'")
//    const fname = "/tmp/compiling.go"
//    writeOut( e.lines, fname )
//    out, err := exec.Run( "/home/chris/bin/gobin/6g", []string{"6g", fname}, os.Environ(), "", exec.DevNull, exec.Pipe, exec.MergeWithStdout )
//    if err != nil { log("oops: ", err); return }
//    var buf bytes.Buffer
//    io.Copy(&buf, out.Stdout)
//    w, err := out.Wait(0)
//    out.Close()
//    log("output: ", string(buf.Bytes()))
//    _ = w
//}
//
//func writeOut(lines []string, fname string) {
//    newline := []byte{'\n'}
//    of, err := os.Open(fname, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0666)
//    if err == nil { 
//        defer of.Close()
//        for _, line := range lines {
//            // HORRID. Will fix. TODO
//            of.WriteString( line )
//            of.Write(newline)
//        }
//    } else {
//        log("oops: ",err)
//    }
//}
//
//func (e *EditPanel) Cursor(line, col int) {
//    e.line, e.col = line, col
//    e.SubPanel.Cursor(line, col)
//}

// -----------------------------------------------------------------------

func NewWindowPanel(w *curses.Window, body Panel) Panel {
    wp := &WindowPanel{window: w, body: body}
    body.Parent(wp)
    return wp
}

type WindowPanel struct {
    window *curses.Window
    lines, cols int
    body Panel
}

func (w *WindowPanel) Refresh() {
    w.window.Clear()
    w.body.Refresh()
}

func (w *WindowPanel) Resize(lines, cols int, lineOffset, colOffset int) {
    w.lines, w.cols = lines, cols
    w.body.Resize(lines, cols, lineOffset, colOffset)
}

func (w *WindowPanel) Cursor(line, col int) {
    w.window.Move(col, line)
}

func (w *WindowPanel) Parent(p Panel) {
    panic("cannot give a WindowPanel a parent")
}

func (w *WindowPanel) Key(rune int) {
    // swallow silently
}

func (w *WindowPanel) FocusForClick(line, col int) Panel {
    return w.body.FocusForClick(line, col)
}

func (w *WindowPanel) Put(line, col int, rune int) {
    w.window.Addch(col, line, int32(rune), 0)
}

func (w *WindowPanel) PutString(line, col int, text string) {
    w.window.Addstr(col, line, text, int32(0))
}
